#include "commands.h"
#include "debug.h"
#include "error.h"
#include "string.h"
#include "utils.h"
#include "logging.h"
#include "file.h"
#include "scoped_ptr.h"
#include "cgi.h"

#define kNameValueChar  _T('=')
#define kTrueValue      _T("true")
#define kFalseValue     _T("false")
#define kOnValue        _T("on")
#define kOffValue       _T("off")

//
// Helper functions
//

template<class T>
HRESULT ConvertValue(const TCHAR* str_value, T* value);

// Convert the three-valued value from the string representation
template<>
HRESULT ConvertValue<ThreeValue>(const TCHAR* str_value, ThreeValue* value)
{
	ASSERT1(value);

	*value = VALUE_NOT_SET;
	if (str_value && *str_value)
	{
		if (String_StrNCmp(str_value, kTrueValue, TSTR_SIZE(kTrueValue) + 1, true) == 0 ||
			String_StrNCmp(str_value, kOnValue, TSTR_SIZE(kOnValue) + 1, true) == 0)
		{
			*value = TRUE_VALUE;
		} 
		else if (String_StrNCmp(str_value, kFalseValue, TSTR_SIZE(kFalseValue) + 1,	true) == 0 ||
			String_StrNCmp(str_value, kOffValue, TSTR_SIZE(kOffValue) + 1, true) == 0)
		{
			*value = FALSE_VALUE;
		} else {
			return CI_E_INVALID_ARG;
		}
	}
	return S_OK;
}

// Convert the int value from the string representation
template<>
HRESULT ConvertValue<int>(const TCHAR* str_value, int* value)
{
	ASSERT1(str_value && *str_value);
	ASSERT1(value);

	if (_set_errno(0)) {
		return E_FAIL;
	}

	*value = _tcstol(str_value, NULL, 0);
	if (errno == ERANGE) {
		return CI_E_INVALID_ARG;
	}
	return S_OK;
}

// Convert the unsigned int value from the string representation
template<>
HRESULT ConvertValue<uint32>(const TCHAR* str_value, uint32* value)
{
	ASSERT1(str_value && *str_value);
	ASSERT1(value);

	if (_set_errno(0)) {
		return E_FAIL;
	}

	*value = _tcstoul(str_value, NULL, 0);
	if (errno == ERANGE) {
		return CI_E_INVALID_ARG;
	}
	return S_OK;
}

// Convert the string value from the string representation
HRESULT ConvertValue(const TCHAR* str_value, CString* value, bool to_unescape)
{
	ASSERT1(str_value && *str_value);
	ASSERT1(value);

	*value = str_value;

	if (to_unescape) {
		int length = value->GetLength();
		scoped_array<TCHAR> unescaped_value(new TCHAR[length + 1]);
		RET_IF_FALSE(CGI::UnescapeString(*value, length, unescaped_value.get(),
			length + 1), CI_E_INVALID_ARG);
		*value = unescaped_value.get();
	}

	return S_OK;
}

//
// Struct CommandOption
//
void CommandOption::Init(const TCHAR* name, CommandOptionType type,
						 void* value, int max_value_len)
{
	this->name = name;
	this->type = type;
	this->value = value;
	this->max_value_len = max_value_len;
}

void CommandOption::Copy(const CommandOption& option)
{
	Init(option.name, option.type, option.value, option.max_value_len);
}

//
// Class CommandParsingSimple
//

// Constructor
CommandParsingSimple::CommandParsingSimple()
: separator_(_T(' '))
{
}

// Constructor
CommandParsingSimple::CommandParsingSimple(TCHAR separator)
: separator_(separator)
{
}

// Parse a command line string into args
HRESULT CommandParsingSimple::ParseSimple(const TCHAR* cmd_line)
{
	ASSERT1(cmd_line);

	UTIL_LOG(L3, (_T("[CommandParsingSimple::ParseSimple][%s]"), cmd_line));

	args_.clear();

	// Split command line string into list of arguments
	for (const TCHAR* s = cmd_line; *s; ++s) {
		// Handle separator
		if (*s == separator_) {
			continue;
		}

		// Handle single/double quote
		if (*s == _T('"') || *s == _T('\'')) {
			int right_quote = String_FindChar(s + 1, *s);
			if (right_quote == -1) {
				UTIL_LOG(LE, (_T("[CommandParsingSimple::ParseSimple]")
					_T("[single/double quote mismatches]")));
				return CI_E_INVALID_ARG;
			}
			args_.push_back(CString(s + 1, right_quote));
			s += right_quote + 1;
			continue;
		}

		// Handle all other char
		int next_space = String_FindChar(s + 1, separator_);
		if (next_space == -1) {
			args_.push_back(CString(s));
			break;
		} else {
			args_.push_back(CString(s, next_space + 1));
			s += next_space + 1;
		}
	}

	return S_OK;
}

// Get the arg at specified position from the command line
HRESULT CommandParsingSimple::GetAt(uint32 position, CString* arg)
{
	ASSERT1(arg);
	ASSERT1(position < args_.size());

	if (!arg || position >= args_.size()) {
		return E_INVALIDARG;
	}

	*arg = args_[position];
	return S_OK;
}

// Remove the arg at specified position from the command line
HRESULT CommandParsingSimple::RemoveAt(uint32 position)
{
	ASSERT1(position < args_.size());

	if (position >= args_.size()) {
		return E_INVALIDARG;
	}

	uint32 i = 0;
	std::vector<CString>::iterator it(args_.begin());
	for (; i < position; ++it, ++i) {
		ASSERT1(it != args_.end());
	}
	args_.erase(it);
	return S_OK;
}

// Converted to the string
HRESULT CommandParsingSimple::ToString(CString* cmd_line)
{
	ASSERT1(cmd_line);

	bool is_first = true;
	cmd_line->Empty();
	for (std::vector<CString>::const_iterator it(args_.begin());
		it != args_.end();
		++it) {
			if (is_first) {
				is_first = false;
			} else {
				cmd_line->AppendChar(separator_);
			}
			const TCHAR* arg = it->GetString();
			if (String_FindChar(arg, separator_) != -1) {
				cmd_line->AppendChar(_T('"'));
				cmd_line->Append(arg);
				cmd_line->AppendChar(_T('"'));
			} else {
				cmd_line->Append(arg);
			}
	}

	return S_OK;
}

// Static Helper function that splits a command line
// string into executable and any arguments
HRESULT CommandParsingSimple::SplitExeAndArgs(const TCHAR* cmd_line,
											  CString* exe,
											  CString* args)
{
	ASSERT1(cmd_line);
	ASSERT1(exe);
	ASSERT1(args);

	// Do the parsing
	CommandParsingSimple cmd_parsing_simple;

	RET_IF_FAILED(cmd_parsing_simple.ParseSimple(cmd_line));
	RET_IF_FAILED(cmd_parsing_simple.GetAt(0, exe));
	exe->Trim();
	RET_IF_FAILED(cmd_parsing_simple.RemoveAt(0));
	return (cmd_parsing_simple.ToString(args));
}

HRESULT CommandParsingSimple::SplitExeAndArgsGuess(const TCHAR* cmd_line,
												   CString* exe,
												   CString* args)
{
	ASSERT1(cmd_line);
	ASSERT1(exe);
	ASSERT1(args);

	if (File::Exists(cmd_line)) {
		// Optimization for the single executable case.
		// Fill the [out] parameters and return.
		*exe = cmd_line;
		exe->Trim();
		args->Empty();
		return S_OK;
	}

	CString command_line(cmd_line);
	// Check if the command line is properly enclosed, or that it does not have
	// spaces
	if (command_line.GetAt(0) != _T('"') && command_line.Find(_T(' ')) != -1) {
		// File::Exists() does not handle leading spaces so remove it.
		command_line.Trim();

		// If not, need to find the executable, and if valid, enclose it in
		// double quotes
		const TCHAR* index_dot_exe = stristrW(command_line.GetString(), _T(".EXE"));

		if (index_dot_exe != NULL) {
			int dot_exe_end = (index_dot_exe - command_line.GetString())
				+ arraysize(_T(".EXE")) - 1;
			if (File::Exists(CString(command_line, dot_exe_end))) {
				// Enclose the EXE in double quotes
				command_line.Insert(dot_exe_end, _T('"'));
				command_line.Insert(0, _T('"'));
			} else {
				UTIL_LOG(L1, (_T("[CommandParsing::SplitExeAndArgsGuess]")
					_T("[Could not guess the Executable file within [%s]. ")
					_T("Passing on to SplitExeAndArgs as-is."),
					command_line));
			}
		}
	}

	// Do the parsing
	return SplitExeAndArgs(command_line, exe, args);
}


// Static Helper function that returns the number of arguments
// in the passed in cmd_line
HRESULT CommandParsingSimple::GetNumberOfArgs(const TCHAR* cmd_line,
											  uint32* number_of_args)
{
	ASSERT1(cmd_line);
	ASSERT1(number_of_args);

	// Do the parsing
	CommandParsingSimple cmd_parsing_simple;

	RET_IF_FAILED(cmd_parsing_simple.ParseSimple(cmd_line));
	*number_of_args = cmd_parsing_simple.args_.size();
	return S_OK;
}


//
// Class CommandParsing
//

// Constructor
CommandParsing::CommandParsing(CommandOption* options, int options_count)
: CommandParsingSimple(),
options_(options),
options_count_(options_count),
as_name_value_pair_(false)
{
}

// Constructor
CommandParsing::CommandParsing(CommandOption* options, int options_count,
							   TCHAR separator, bool as_name_value_pair)
							   : CommandParsingSimple(separator),
							   options_(options),
							   options_count_(options_count),
							   as_name_value_pair_(as_name_value_pair)
{
}

// Parse a command line string
HRESULT CommandParsing::Parse(const TCHAR* cmd_line, bool ignore_unknown_args)
{
	ASSERT1(cmd_line);

	UTIL_LOG(L3, (_T("[CommandParsing::Parse][%s][%d]"),
		cmd_line, ignore_unknown_args));

	// Parse into args_ vector
	RET_IF_FAILED(ParseSimple(cmd_line));

	// Do the internal parsing
	return InternalParse(ignore_unknown_args);
}

// Parse a list of command line arguments
HRESULT CommandParsing::ParseArguments(int argc, TCHAR* argv[])
{
	if (argc <= 1) {
		return S_OK;
	}

	// Push each argument
	args_.clear();
	for (int i = 1; i < argc; ++i) {
		args_.push_back(CString(argv[i]));
	}

	// Do the internal parsing
	return InternalParse(false);
}

// Internal parsing
HRESULT CommandParsing::InternalParse(bool ignore_unknown_args)
{
	CString name, value;
	for (std::vector<CString>::const_iterator it(args_.begin()); it != args_.end(); ++it)
	{
		RET_IF_FAILED(ExtractName(&name, &it));

		int i = FindOption(name);
		if (i == -1) {
			if (ignore_unknown_args) {
				UTIL_LOG(L3, (_T("[CommandParsing::Parse][unknown arg %s]"), name));
				continue;
			} else {
				UTIL_LOG(LE, (_T("[CommandParsing::Parse][invalid arg %s]"), name));
				return CI_E_INVALID_ARG;
			}
		}

		if (options_[i].type != COMMAND_OPTION_BOOL) 
			RET_IF_FAILED(ExtractValue(options_[i], &value, &it, args_.end()));

		switch (options_[i].type & COMMAND_OPTION_FLAGS_MASK)
		{
		case COMMAND_OPTION_BOOL:
			{
				bool bool_value = true;
				SetParsedValue(options_[i], bool_value);
				break;
			}

		case COMMAND_OPTION_THREE:
			{
				ThreeValue three_value = VALUE_NOT_SET;
				RET_IF_FAILED(ConvertValue(value, &three_value));
				SetParsedValue(options_[i], three_value);
				break;
			}

		case COMMAND_OPTION_INT: 
			{
				int int_value = 0;
				RET_IF_FAILED(ConvertValue(value, &int_value));
				SetParsedValue(options_[i], int_value);
				break;
			}

		case COMMAND_OPTION_UINT:
			{
				int uint_value = 0;
				RET_IF_FAILED(ConvertValue(value, &uint_value));
				SetParsedValue(options_[i], uint_value);
				break;

			}

		case COMMAND_OPTION_STRING:
			{
				CString str_value;
				bool is_unescape = (options_[i].type & COMMAND_OPTION_UNESCAPE) != 0;
				RET_IF_FAILED(ConvertValue(value, &str_value, is_unescape));
				SetParsedValue(options_[i], str_value);
				break;
			}

		default:
			ASSERT1(false);
			break;
		}
	}

	return S_OK;
}

// Extract the name
HRESULT CommandParsing::ExtractName(CString* name,
									std::vector<CString>::const_iterator* it)
{
	ASSERT1(name);
	ASSERT1(it);

	if (as_name_value_pair_) {
		int idx = (*it)->Find(kNameValueChar);
		if (idx == -1) {
			return CI_E_INVALID_ARG;
		} else {
			*name = (*it)->Left(idx);
		}
	} else {
		*name = (*it)->GetString();
	}
	return S_OK;
}

// Extract the value
// Also validate the value length if necessary
HRESULT CommandParsing::ExtractValue(
									 const CommandOption& option,
									 CString* value,
									 std::vector<CString>::const_iterator* it,
									 const std::vector<CString>::const_iterator& end)
{
	ASSERT1(value);
	ASSERT1(it);

	if (as_name_value_pair_) {
		int idx = (*it)->Find(kNameValueChar);
		if (idx == -1) {
			return CI_E_INVALID_ARG;
		} else {
			*value = (*it)->Right((*it)->GetLength() - idx - 1);
		}
	} else {
		++(*it);
		if (*it == end) {
			UTIL_LOG(LE, (_T("[CommandParsing::ExtractValue]")
				_T("[argument %s missing value]"), option.name));
			return CI_E_INVALID_ARG;
		}
		*value = (*it)->GetString();
	}

	if (option.max_value_len >= 0) {
		if (value->GetLength() > option.max_value_len) {
			return CI_E_INVALID_ARG;
		}
	}

	return S_OK;
}

// Set the parsed value
template<class T>
void CommandParsing::SetParsedValue(const CommandOption& option,
									const T& value)
{
	if (option.type & COMMAND_OPTION_MULTIPLE)
	{
		ASSERT((option.type & COMMAND_OPTION_FLAGS_MASK) != COMMAND_OPTION_BOOL,
			(_T("COMMAND_OPTION_BOOL can't be used with COMMAND_OPTION_MULTIPLE")));
		ASSERT((option.type & COMMAND_OPTION_FLAGS_MASK) != COMMAND_OPTION_THREE,
			(_T("COMMAND_OPTION_THREE can't be used with COMMAND_OPTION_MULTIPLE")));

		std::vector<T>* ptr = reinterpret_cast<std::vector<T>*>(option.value);
		ptr->push_back(value);
	} else {
		T* ptr = reinterpret_cast<T*>(option.value);
		*ptr = value;
	}
}

// Helper function to find an option in the CommandOption list
int CommandParsing::FindOption(const TCHAR* option_name)
{
	ASSERT1(option_name);

	for (int i = 0; i < options_count_; ++i)
	{
		if (String_StrNCmp(option_name,
			options_[i].name,
			options_[i].name.GetLength() + 1,
			false) == 0) 
				return i;
	}

	return -1;
}

// Remove an option from the command line
HRESULT CommandParsing::Remove(const TCHAR* option_name)
{
	ASSERT1(option_name);

	for (std::vector<CString>::iterator it(args_.begin()); it != args_.end(); ++it)
	{
		if (*it == option_name)
		{
			int i = FindOption(option_name);
			if (i == -1)
				return E_FAIL;

			args_.erase(it);
			if (!as_name_value_pair_)
			{
				if (options_[i].type != COMMAND_OPTION_BOOL)
				{
					if (it == args_.end())
						return E_FAIL;

					args_.erase(it);
				}
			}

			return S_OK;
		}
	}

	return E_FAIL;
}